﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace TurtlePoker
{   
    abstract class Player
    {
        public string m_name;
        protected List<Card> m_cards;

        // basic moves
        // deal move is the same to all player types
        protected void deal(CardManager cm)
        {
            Card c = cm.deal();
            // Console.WriteLine("{0} deals card: {1}", this.ToString(), c.ToString());
            this.m_cards.Add(c);
            this.m_cards.Sort();
        }
        // other moves need i/o or not depending on the player type
        abstract protected void put(CardManager cm);
        abstract protected void replace(CardManager cm);
        abstract protected void replace_one(CardManager cm);
        // basic operations
        // each operation consists of several moves 
        // and some i/o or not (depending on the player type)
        abstract public int init_op(CardManager cm);
        abstract public void start_op(CardManager cm);
        abstract public void normal_op(CardManager cm);
        abstract public DecideChoice decide_op(CardManager cm);
        abstract public FinalResult final_op(CardManager cm, Player p);
        // check if the player wanna enter final mode
        abstract public bool enter_final(CardManager cm);
        // return cards held
        public List<Card> get_cards()
        {
            return this.m_cards;
        }
        // display the player's cards
        public void print_cards()
        {
            Console.Write("{0} owns cards:", this.ToString());
            foreach (Card c in this.m_cards)
            {
                Console.Write(c.ToString());
            }
            Console.Write("\n");
        }
        
        override public string ToString()
        {
            return "<" + this.m_name + ">";
        }
    }

    class HumanPlayer : Player
    {
        public HumanPlayer()
        {
            this.m_cards = new List<Card>();
            this.m_name = "Default";
        }

        public HumanPlayer(string name)
        {
            this.m_cards = new List<Card>();
            this.m_name = name;
        }

        public HumanPlayer(List<Card> cards, string name = "Default")
        {
            this.m_cards = cards;
            this.m_name = name;
        }

        // based on stdio
        // put 1 card into replaceable card pile
        override protected void put(CardManager cm)
        {
            this.print_cards();
            Console.WriteLine("{0}, input the card index you wanna put[put].", this.ToString());
            string s = Console.ReadLine();
            int card_index = int.Parse(s);
            Debug.Assert(card_index < this.m_cards.Count);
            cm.put(this.m_cards[card_index]);
            this.m_cards.RemoveAt(card_index);
        }

        // based on stdio
        // replace cards with one's all cards in hand
        override protected void replace(CardManager cm)
        {
            List<Card> new_cards = new List<Card>(cm.get_card_rep());

            Console.WriteLine("{0}, choose the cards you wanna put.", this.ToString());

            bool[] chosen = new bool[this.m_cards.Count];
            chosen.Initialize(); // initialize to false
            Debug.Assert(chosen[0] == false);

            List<Card> cards_in = new List<Card>();
            for (int i = 0; i < new_cards.Count; i++)
            {
                int index = int.Parse(Console.ReadLine());
                Debug.Assert(index < this.m_cards.Count);
                Debug.Assert(chosen[index] == false); // avoid duplicate choice
                cards_in.Add(this.m_cards[index]);
                chosen[index] = true;
            }

            foreach (Card c in cards_in)
            {
                this.m_cards.Remove(c);
            }

            cm.replace(cards_in);
            foreach (Card c in new_cards)
            {
                this.m_cards.Add(c);
            }
            this.m_cards.Sort();
        }

        // based on stdio
        // replace one card
        override protected void replace_one(CardManager cm)
        {

            Console.WriteLine("{0}, input the card index you wanna put[replace].", this.ToString());
            int card_in = int.Parse(Console.ReadLine());
            Debug.Assert(card_in < this.m_cards.Count);

            Console.WriteLine("{0}, input the card index you wanna get[replace].", this.ToString());
            int card_out = int.Parse(Console.ReadLine());
            Debug.Assert(card_out < cm.get_card_rep().Count);
            // add specified card
            this.m_cards.Add(cm.get_card_rep()[card_out]);
            cm.replace_one(this.m_cards[card_in], cm.get_card_rep()[card_out]);
            // remove card
            this.m_cards.RemoveAt(card_in);
            this.m_cards.Sort();
        }

        // only deal card
        // return cards number after dealing
        override public int init_op(CardManager cm)
        {
            Debug.Assert(this.m_cards.Count < Game.CARD_HOLD_LIMIT);
            this.deal(cm);
            return this.m_cards.Count;
        }

        override public void start_op(CardManager cm)
        {
            Debug.Assert(this.m_cards.Count == Game.CARD_HOLD_LIMIT);
            Debug.Assert(cm.get_card_rep().Count == 0);

            this.deal(cm);
            this.put(cm);
        }

        override public void normal_op(CardManager cm)
        {
            Debug.Assert(this.m_cards.Count == Game.CARD_HOLD_LIMIT);
            Debug.Assert(cm.get_card_rep().Count > 0);
            Debug.Assert(cm.get_card_rep().Count < Game.CARD_REP_LIMIT);
            this.print_cards();
            // get answer from player
            Console.WriteLine("{0}, which operation do you choose?", this.ToString());
            Console.WriteLine("1. Deal and put.");
            Console.WriteLine("2. Replace all cards.");
            int res = int.Parse(Console.ReadLine());
            Debug.Assert(res == 1 || res == 2);
            if (res == 1)
            {
                this.deal(cm);
                this.put(cm);
                return;
            }
            if (res == 2)
            {
                this.replace(cm);
                return;
            }           
        }

        override public DecideChoice decide_op(CardManager cm)
        {
            Debug.Assert(this.m_cards.Count == Game.CARD_HOLD_LIMIT);
            Debug.Assert(cm.get_card_rep().Count == Game.CARD_REP_LIMIT);
            this.print_cards();
            Console.WriteLine("{0}, which operation do you choose?", this.ToString());
            Console.WriteLine("1. Replace.");
            Console.WriteLine("2. Give up.");
            int res = int.Parse(Console.ReadLine());
            Debug.Assert(res == 1 || res == 2);
            if (res == 1) 
            {
                this.replace_one(cm);
                return DecideChoice.Replace;
            }
            if (res == 2) 
            {
                return DecideChoice.GiveUp;
            }
            else
                throw new NotImplementedException();
        }

        override public bool enter_final(CardManager cm)
        {
            this.print_cards();
            Console.WriteLine("{0}, do you wanna enter final mode?", this.ToString());
            Console.WriteLine("1. Yes.");
            Console.WriteLine("2. No.");
            int res = int.Parse(Console.ReadLine());
            Debug.Assert(res == 1 || res == 2);
            return (res == 1);
        }

        override public FinalResult final_op(CardManager cm, Player p)
        {
            this.print_cards();
            Console.WriteLine("{0}, do you wanna suppress {1}?", this.ToString(), p.ToString());
            Console.WriteLine("1. Yes.");
            Console.WriteLine("2. No.");
            int res = int.Parse(Console.ReadLine());
            Debug.Assert(res == 1 || res == 2);
            if (res == 1)
            {
                return FinalResult.Suppress;
            }
            if (res == 2)
            {
                return FinalResult.NotSuppress;
            }
            else
                throw new NotImplementedException();
        }        
    }
}
